package megaezgo

import (
	"encoding/json"
	"fmt"
	"bytes"
	"io/ioutil"
	"net/http"
	"time"
	"math/big"
	"crypto/rsa"
	"crypto/x509"
    "encoding/pem"
	"crypto/sha256"
	"encoding/base64"
	"github.com/pkg/errors"
	"github.com/astaxie/beego"
)

var (
    ErrInputSize  = errors.New("input size too large")
    ErrEncryption = errors.New("encryption error")
)

/***********************************/
type Auth struct {
	version     string
	apiStoreId  string
	terminalIp  string
	serviceName string
	requrl      string
	apiStorePw  string
	keyPEMBlock []byte
}

func NewSDK(apiStoreId,terminalIp,restURL,apiStorePw string,keyPEMBlock []byte) *Auth {
	return &Auth{version:"1.0",apiStoreId:apiStoreId,terminalIp:terminalIp,serviceName:"WeiXin",
	             requrl:restURL,apiStorePw:apiStorePw,keyPEMBlock:keyPEMBlock}
}

func (this *Auth) PayJsonExcute(paymentType string,request,rspDataValue interface{}) ([]byte, error) {
	dataValue, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}
	var req struct {
		ReqHeader struct {
			Version     string  //版本
			ApiStoreId  string  //店家ID
			TerminalIp  string  //终端IP 
			ServiceName string  //服务名称 
			HashValue   string  // 
		} `json:"Header,omitempty"`
		ReqData struct {
			APIStorePw  string `json:"APIStorePw"`
			TradeSign   string `json:"TradeSign"`
			PaymentType string `json:"PaymentType"`
			DataValue   string `json:"DataValue"`
		} `json:"Data,omitempty"`	
	}
	req.ReqHeader.Version = this.version
	req.ReqHeader.ApiStoreId = this.apiStoreId
	req.ReqHeader.TerminalIp = this.terminalIp
	req.ReqHeader.ServiceName = this.serviceName
	req.ReqData.APIStorePw = this.apiStorePw
	req.ReqData.TradeSign = time.Now().Format("2006-01-02 15:04:05")
	req.ReqData.PaymentType = paymentType
	req.ReqData.DataValue = string(dataValue)
	signValue, err := json.Marshal(req.ReqData)
	if err != nil {
		return nil, err
	}
	//beego.Debug("signValue:", string(signValue))
	if tmpSign, err := rsaSign(signValue, this.keyPEMBlock); err != nil {
	   beego.Debug("tmpSign err:", err.Error())
	   return nil, err
	}else{
	   req.ReqHeader.HashValue = tmpSign
	}
	body, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	reqHttp, err := http.NewRequest("POST", this.requrl, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHttp.Header.Set("Content-Type", "application/json") //必须
	reqHttp.Header.Set("Accept", "application/json") //必须
	client := &http.Client{}
	
	resp, err := client.Do(reqHttp)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	beego.Debug("Megaezgo:",paymentType," rsp data:", string(data))
	var response struct {
		RspHeader struct {
			StatusCode   string  `json:"StatusCode"`
			StatusDesc   string  `json:"StatusDesc"`
			ResponseTime string  `json:"ResponseTime,omitempty"`
		} `json:"Header,omitempty"`
		RsqData struct {
			DataValue   string `json:"DataValue"`
		} `json:"Data,omitempty"`
	}
	err = json.Unmarshal(data, &response)
	if err != nil {
	    return nil, err
	}
	if response.RspHeader.StatusCode != "0000" {
	    return nil, errors.New(fmt.Sprintf("StatusCode: %s,StatusDesc: %s",
		             response.RspHeader.StatusCode,response.RspHeader.StatusDesc))
	}
	err = json.Unmarshal([]byte(response.RsqData.DataValue), &rspDataValue)
	if err != nil {
	    return nil, err
	}
	return data, nil
}

func (this *Auth) RefundJsonExcute(paymentType string,request,rspDataValue interface{}) ([]byte, error) {
	dataValue, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}
	var req struct {
		ReqHeader struct {
			Version     string  //版本
			ApiStoreId  string  //店家ID
			TerminalIp  string  //终端IP 
			ServiceName string  //服务名称 
			HashValue   string  // 
		} `json:"Header,omitempty"`
		ReqData struct {
			APIStorePw  string `json:"APIStorePw"`
			TradeSign   string `json:"TradeSign"`
			PaymentType string `json:"PaymentType"`
			DataValue   string `json:"DataValue"`
		} `json:"Data,omitempty"`	
	}
	req.ReqHeader.Version = this.version
	req.ReqHeader.ApiStoreId = this.apiStoreId
	req.ReqHeader.TerminalIp = this.terminalIp
	req.ReqHeader.ServiceName = this.serviceName
	req.ReqData.APIStorePw = this.apiStorePw
	req.ReqData.TradeSign = time.Now().Format("2006-01-02 15:04:05")
	req.ReqData.PaymentType = paymentType
	req.ReqData.DataValue = string(dataValue)
	signValue, err := json.Marshal(req.ReqData)
	if err != nil {
		return nil, err
	}
	//beego.Debug("signValue:", string(signValue))
	if tmpSign, err := rsaSign(signValue, this.keyPEMBlock); err != nil {
	   beego.Debug("tmpSign err:", err.Error())
	   return nil, err
	}else{
	   req.ReqHeader.HashValue = tmpSign
	}
	body, err := json.Marshal(req)
	if err != nil {
		return nil, err
	}
	beego.Debug("Megaezgo req data:", string(body))
	reqHttp, err := http.NewRequest("POST", this.requrl, bytes.NewBuffer(body))
	if err != nil {
		return nil, err
	}
	reqHttp.Header.Set("Content-Type", "application/json") //必须
	reqHttp.Header.Set("Accept", "application/json") //必须
	client := &http.Client{}
	
	resp, err := client.Do(reqHttp)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	beego.Debug("Megaezgo:",paymentType," rsp data:", string(data))
	var response struct {
		RspHeader struct {
			StatusCode   string  `json:"StatusCode"`
			StatusDesc   string  `json:"StatusDesc"`
			ResponseTime string  `json:"ResponseTime,omitempty"`
		} `json:"Header,omitempty"`
		RsqData struct {
			DataValue   string `json:"DataValue"`
		} `json:"Data,omitempty"`
	}
	err = json.Unmarshal(data, &response)
	if err != nil {
	    return nil, err
	}
	if response.RspHeader.StatusCode != "9997" {
	    return nil, errors.New(fmt.Sprintf("StatusCode: %s,StatusDesc: %s",
		             response.RspHeader.StatusCode,response.RspHeader.StatusDesc))
	}
	err = json.Unmarshal([]byte(response.RsqData.DataValue), &rspDataValue)
	if err != nil {
	    return nil, err
	}
	return data, nil
}

// SHA256 sha256
func sHA256(data []byte) []byte {
	h := sha256.New()
	h.Write(data)
	return h.Sum(nil)
}

// Base64Encode base64 encode
func base64Encode(data []byte) string {
	return base64.StdEncoding.EncodeToString(data)
}

func PrivateEncrypt(priv *rsa.PrivateKey, data []byte) (enc []byte, err error) {
    k := (priv.N.BitLen() + 7) / 8
    tLen := len(data)
    // rfc2313, section 8:
    // The length of the data D shall not be more than k-11 octets
    if tLen > k-11 {
        err = ErrInputSize
        return
    }
    em := make([]byte, k)
    em[1] = 1
    for i := 2; i < k-tLen-1; i++ {
        em[i] = 0xff
    }
    copy(em[k-tLen:k], data)
    c := new(big.Int).SetBytes(em)
    if c.Cmp(priv.N) > 0 {
        err = ErrEncryption
        return
    }
    var m *big.Int
    var ir *big.Int
    if priv.Precomputed.Dp == nil {
        m = new(big.Int).Exp(c, priv.D, priv.N)
    } else {
        // We have the precalculated values needed for the CRT.
        m = new(big.Int).Exp(c, priv.Precomputed.Dp, priv.Primes[0])
        m2 := new(big.Int).Exp(c, priv.Precomputed.Dq, priv.Primes[1])
        m.Sub(m, m2)
        if m.Sign() < 0 {
            m.Add(m, priv.Primes[0])
        }
        m.Mul(m, priv.Precomputed.Qinv)
        m.Mod(m, priv.Primes[0])
        m.Mul(m, priv.Primes[1])
        m.Add(m, m2)

        for i, values := range priv.Precomputed.CRTValues {
            prime := priv.Primes[2+i]
            m2.Exp(c, values.Exp, prime)
            m2.Sub(m2, m)
            m2.Mul(m2, values.Coeff)
            m2.Mod(m2, prime)
            if m2.Sign() < 0 {
                m2.Add(m2, prime)
            }
            m2.Mul(m2, values.R)
            m.Add(m, m2)
        }
    }

    if ir != nil {
        // Unblind.
        m.Mul(m, ir)
        m.Mod(m, priv.N)
    }
    enc = m.Bytes()
    return
}

func rsaSign(origdata, privateKey []byte) (string, error) {
	digest := sHA256(origdata)
	//fmt.Println("digest:", digest)
	//fmt.Println("digest base64:", base64Encode(digest))
	block, _ := pem.Decode(privateKey)
	if block == nil {
		return "", errors.New("private key error")
	}
	privkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		return "", err
	}
	encData, err := PrivateEncrypt(privkey, []byte(base64Encode(digest)))
	if err != nil {
		return "", err
	}
	return base64Encode(encData), nil
}
